<?php

namespace App\Http\Controllers;

use App\Helpers\Utils;
use App\Models\Deposit;
use App\Models\DepositType;
use App\Models\Dorm;
use App\Models\DormMeter;
use App\Models\DormMeterRecord;
use App\Models\ElectricFeeType;
use App\Models\StaffDormAdjust;
use App\Models\StaffPeriodFee;
use App\Models\StaffPeriodFeeDetail;
use App\Presenters\PeriodPresenter;
use App\Models\StaffDormHistory;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Prettus\Validator\Exceptions\ValidatorException;
use App\Http\Requests\PeriodCreateRequest;
use App\Http\Requests\PeriodUpdateRequest;
use App\Repositories\PeriodRepository;
use Illuminate\Http\Request;

/**
 * Class PeriodsController.
 *
 * @package namespace App\Http\Controllers;
 */
class PeriodsController extends Controller
{
    /**
     * @var PeriodRepository
     */
    protected $repository;

    /**
     * PeriodsController constructor.
     *
     * @param PeriodRepository $repository
     * @param PeriodValidator $validator
     */
    public function __construct(PeriodRepository $repository)
    {
        $this->repository = $repository;
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        $this->repository->pushCriteria(app('Prettus\Repository\Criteria\RequestCriteria'));
        if (\Route::currentRouteName() == 'api.periods.index') {
            if (!$request->get('orderBy') && !$request->get('status')) {
                $periods = $this->repository->orderBy('created_at', 'desc')->where('status', '<', 2)->limit($request->get('limit', 5))->get();
            } else {
                $periods = $this->repository->whereNotNull('name')->limit($request->get('limit', 5))->get();
            }
        } else {
            $this->repository->setPresenter(PeriodPresenter::class);
            $periods = $this->repository->orderBy('created_at', 'desc')->paginate($request->get('limit', 20));
        }
        if (request()->wantsJson()) {

            return response()->json([
                'data' => $periods,
            ]);
        }

        return view('periods.index', compact('periods'));
    }

    public function create()
    {
        $latestRecord = DormMeterRecord::orderBy('created_at', 'desc')->first();
        if ($latestRecord) {
            $lastDate = $latestRecord->created_at->format('Y-m-d');
        } else {
            $lastDate = Carbon::now()->toDateString();
        }
        return view('periods.create', compact('lastDate'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  PeriodCreateRequest $request
     *
     * @return \Illuminate\Http\Response
     *
     * @throws \Prettus\Validator\Exceptions\ValidatorException
     */
    public function store(PeriodCreateRequest $request)
    {
        try {
            $year = $request->get('year');
            $month = $request->get('month');
            $count = $this->repository->findWhere(['year' => $year, 'month' => $month])->count();
            if ($count > 0) {
                if ($request->wantsJson()) {
                    return response()->json([
                        'error' => true,
                        'message' => '已经存在此周期'
                    ]);
                }

                return redirect()->back()->withErrors('已经存在此周期')->withInput();
            }
            $start_date = Carbon::create($year, $month, 1);
            $start = $start_date->format('Y-m-d');
            $end = $start_date->lastOfMonth()->format('Y-m-d');

            $request->merge(['start_date' => $start, 'end_date' => $end]);
            $period = $this->repository->create($request->all());

            $response = [
                'message' => 'Period created.',
                'data' => $period->toArray(),
            ];

            if ($request->wantsJson()) {

                return response()->json($response);
            }

            return redirect()->back()->with('message', $response['message']);
        } catch (ValidatorException $e) {
            if ($request->wantsJson()) {
                return response()->json([
                    'error' => true,
                    'message' => $e->getMessageBag()
                ]);
            }

            return redirect()->back()->withErrors($e->getMessageBag())->withInput();
        }
    }

    /**
     * Display the specified resource.
     *
     * @param  int $id
     *
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $period = $this->repository->find($id);

        if (request()->wantsJson()) {

            return response()->json([
                'data' => $period,
            ]);
        }

        return view('periods.show', compact('period'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int $id
     *
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $period = $this->repository->find($id);
        $latestRecord = DormMeterRecord::orderBy('created_at', 'desc')->first();
        if ($latestRecord) {
            $lastDate = $latestRecord->created_at->format('Y-m-d');
        } else {
            $lastDate = Carbon::now()->toDateString();
        }
        return view('periods.edit', compact('period', 'lastDate'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  PeriodUpdateRequest $request
     * @param  string $id
     *
     * @return Response
     *
     * @throws \Prettus\Validator\Exceptions\ValidatorException
     */
    public function update(PeriodUpdateRequest $request, $id)
    {
        try {

            //确认（审核）计算结果时，更新押金列表
            if ($request->get('status') == 2) {
                Deposit::where('period_id', $id)->where('status', 0)->update(['status' => 1, 'deposit_date' => Carbon::now()->format('Y-m-d')]);
                $period = $this->repository->find($id);
                $adjustInPeriod = StaffDormAdjust::where([
                    ['adjust_date', '>=', $period->start_date],
                    ['adjust_date', '<=', $period->end_date]
                ])->get();
                $outStaffIds = $adjustInPeriod->where('adjust_type', 'out')->pluck('staff_id');
                Deposit::whereIn('staff_id', $outStaffIds)->where('status', 1)->update(['status' => -1]);
                //将人员调整归档
                foreach ($adjustInPeriod as $item) {
                    $item->period_id = $id;
                    $item->save();
                }
                //归档住宿记录
                StaffDormHistory::query()->where('year', $period->year)->where('month', $period->month)->update(['archived' => 1]);
            }

            $period = $this->repository->update($request->all(), $id);

            $response = [
                'message' => 'Period updated.',
                'data' => $period->toArray(),
            ];
            if ($request->wantsJson()) {

                return response()->json($response);
            }
            return redirect()->back()->with('message', $response['message']);
        } catch (\Exception $e) {
            if ($request->wantsJson()) {
                return response()->json([
                    'error' => true,
                    'message' => $e->getMessage()
                ]);
            }
            return redirect()->back()->withErrors($e->getMessage())->withInput();
        }
    }


    /**
     * Remove the specified resource from storage.
     *
     * @param  int $id
     *
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $period = $this->repository->find($id);
        if ($period->status > 1) {
            if (request()->wantsJson()) {
                return response()->json([
                    'error' => true,
                    'message' => 'delete fail, period calc result was confirm',
                ]);
            }
            return redirect()->back()->withErrors('delete fail, period calc result was confirm');
        }
        $deleted = $this->repository->delete($id);
        StaffPeriodFeeDetail::query()->where('period_id', $id)->delete();
        StaffPeriodFee::query()->where('period_id', $id)->delete();
        DormMeterRecord::query()->where('period_id', $id)->update(['period_id' => null]);
        if (request()->wantsJson()) {
            return response()->json([
                'message' => 'Period deleted.',
                'deleted' => $deleted,
            ]);
        }
        return redirect()->back()->with('message', 'Period deleted.');
    }

    /***
     * 查询未抄的宿舍
     * @param $id
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function not_rec($id)
    {
        $period = $this->repository->find($id);
        $ids = DormMeterRecord::query()->where('period_id', $id)->pluck('dorm_meter_id');
        /*当前周期在用的所有表：在周期结束前表已经创建，且更新日期大于等于周期开始后*/
        $meters = DormMeter::with(['dorm:id,building_id,code', 'dorm.building:id,name', 'meter_type:id,name'])
            ->where('install_date', '<=', $period->end_date) //期间结束前创建的表，才需要检查
            ->where('expire_date', '>=', $period->start_date)
            ->whereNotIn('id', $ids)
            ->get(['id', 'dorm_id', 'meter_type_id', 'last_reading', 'last_reading_date', 'active', 'nfc_id']);
        return view('periods.not_rec', compact('meters', 'period'));
    }

    public function un_verify($id)
    {
        try {
            $period = $this->repository->find($id);
            Deposit::where('period_id', $id)->where('status', 1)->update(['status' => 0, 'deposit_date' => Carbon::now()]);
            $adjustInPeriod = StaffDormAdjust::where([
                ['adjust_date', '>=', $period->start_date],
                ['adjust_date', '<=', $period->end_date]
            ])->get();
            $outStaffIds = $adjustInPeriod->where('adjust_type', 'out')->pluck('staff_id');
            //搬出的宿舍的人的押金恢复为已经收取
            Deposit::whereIn('staff_id', $outStaffIds)->where('status', -1)->update(['status' => 1]);
            //将人员调整归档
            foreach ($adjustInPeriod as $item) {
                $item->period_id = null;
                $item->save();
            }
            //归档住宿记录
            StaffDormHistory::query()->where('year', $period->year)->where('month', $period->month)->update(['archived' => 0]);
            $period->status = 1;
            $period->save();
            $response = [
                'message' => '弃审成功',
                'data' => null
            ];
            if (\request()->wantsJson()) {
                return response()->json($response);
            }
            return redirect()->back()->with('message', $response['message']);
        } catch (\Exception $exception) {
            if (\request()->wantsJson()) {
                return response()->json([
                    'error' => true,
                    'message' => $exception->getMessage() . ':' . $exception->getTraceAsString()
                ]);
            }
            return redirect()->back()->withErrors($exception->getMessage());
        }
    }

    /***
     * 计算周期费用核心方法
     * @param $id
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
     */
    public function calc($id)
    {
        set_time_limit(0);
        $period = $this->repository->find($id);
        StaffPeriodFee::query()->where('period_id', $id)->delete();
        StaffPeriodFeeDetail::query()->where('period_id', $id)->delete();
        //StaffDormHistory::query()->where('year',$period->year)->where('month',$period->month)->delete();
        $old_status = $period->status;
        try {
            $adjustInPeriod = StaffDormAdjust::where('adjust_date', '>=', $period->start_date)->get();
            $outStaffIds = $adjustInPeriod->where('adjust_type', 'out')->pluck('staff_id');
            $inStaffIds = $adjustInPeriod->where('adjust_type', 'in')->pluck('staff_id');

            if ($period->status == 100) {
                $msg = '后台正在执行计算中，请稍后在执行计算';
                if (\request()->wantsJson()) {
                    return response()->json([
                        'error' => true,
                        'message' => $msg
                    ]);
                }
                return redirect()->back()->withErrors($msg);
            }
            $period->status = 100;
            $period->save();

            //计算并保存员工当期在各个宿舍的入住天数
            \Artisan::call('calc:dorms', ['period_id' => $period->id]);


            //本期抄表记录
            $recordsInPeriod = DormMeterRecord::where('period_id', $id)->get();

            foreach ($recordsInPeriod as $item) {
                $period->dormMeterRecords()->save($item);
            }

            $feeTypes = Utils::get_fee_types();

            $grpRecs = $recordsInPeriod->groupBy(['dorm_id', 'meter_type_id']);


            //本期某员工在某宿舍的入住天数的集合
            $staffDays = StaffDormHistory::where('year', $period->year)->where('month', $period->month)->groupBy(['staff_id', 'dorm_id'])->selectRaw('sum(days) as days,staff_id,dorm_id')->get();
            //本期某宿舍总的入住人天的集合
            $dormDays = StaffDormHistory::where('year', $period->year)->where('month', $period->month)->groupBy('dorm_id')->selectRaw('sum(days) as days,count(staff_id) as staffs,dorm_id')->get();


            //创建或更新 staffPeriodFeeDetail, 并保存返回对象
            $staffPeriodFeeDetails = collect();

            foreach ($staffDays as $staffDay) {
                $dormId = $staffDay->dorm_id;
                $dormDay = $dormDays->where('dorm_id', $dormId)->first();
                $staffId = $staffDay->staff_id;
                $days = $staffDay->days;
                $totalDays = $dormDay->days;
                $staffCnt = $dormDay->staffs;
                $dorm = Dorm::with('dormType')->find($dormId);

                if ($days > 0 && $totalDays > 0) {
                    foreach ($feeTypes as $type) {
                        $type_name = $type->type_name;
                        $feeType = $dorm->dormType->$type_name;
                        if ($feeType) {
                            $method = $type->calc_method;
                            if ($type->meter_type_id) {
                                //计量表的费用类型，按计量值来计算费用
                                if ($grpRecs->has($dormId)) {
                                    $dormRecs = $grpRecs[$dormId];
                                    if ($dormRecs->has($type->meter_type_id)) {
                                        $totalConsume = $dormRecs[$type->meter_type_id]->sum(function ($rec) {
                                            return $rec->consume;
                                        });
                                        $totalMoney = $this->$method($feeType, $totalConsume, $period->month);
                                        $arr = [
                                            'staff_id' => $staffId,
                                            'dorm_id' => $dormId,
                                            'period_id' => $id,
                                            'staff_person_days' => $days,
                                            'dorm_person_days' => $totalDays,
                                            'total_resource_consume' => $totalConsume,
                                            'total_money' => $totalMoney,
                                            'fee_type' => $type->model,
                                            'fee_id' => $feeType->id,
                                            'fee_undertake' => $totalMoney * $days / $totalDays
                                        ];
                                        $detail = StaffPeriodFeeDetail::query()->create($arr);
                                        $staffPeriodFeeDetails->push($detail);
                                    }
                                }
                            } else {
                                //不含计量表的费用类型
                                $fee = $this->$method($feeType, $staffCnt);

                                //入住时间不超过7天的免收管理费
                                if ($fee > 0 && $days > 7) {
                                    $arr = [
                                        'staff_id' => $staffId,
                                        'dorm_id' => $dormId,
                                        'period_id' => $id,
                                        'staff_person_days' => $days,
                                        'dorm_person_days' => $totalDays,
                                        'total_resource_consume' => 0,
                                        'total_money' => $feeType->compute_unit == '元/月/间' ? $feeType->fee : $feeType->fee * $staffCnt,
                                        'fee_type' => $type->model,
                                        'fee_id' => $feeType->id,
                                        'fee_undertake' => $fee
                                    ];

                                    //管理费以最后入住的宿舍为准，所以之前的记录都要排除掉，避免重复收取
                                    $staffPeriodFeeDetails = $staffPeriodFeeDetails->reject(function ($v) use ($staffId, $id, $type) {
                                        return $v->staff_id == $staffId && $v->period_id == $id && $v->fee_type == $type->model;
                                    });
                                    $detail = StaffPeriodFeeDetail::query()->create($arr);
                                    $staffPeriodFeeDetails->push($detail);
                                }
                            }
                        }
                    }
                }

                $depositTypes = optional(optional($dorm)->dormType)->depositTypes;
                if ($depositTypes) {
                    //搬入宿舍的押金费用明细的计算
                    if ($inStaffIds->contains($staffId)) {
                        foreach ($depositTypes as $depositType) {
                            if ($depositType->fee > 0) {
                                $arr = [
                                    'staff_id' => $staffId,
                                    'dorm_id' => $dormId,
                                    'period_id' => $id,
                                    'staff_person_days' => $days,
                                    'dorm_person_days' => $totalDays,
                                    'total_resource_consume' => 0,
                                    'total_money' => 0,
                                    'fee_type' => DepositType::class,
                                    'fee_id' => $depositType->id,
                                    'fee_undertake' => $depositType->fee
                                ];
                                $deposit_arr = ['staff_id' => $staffId, 'deposit_type_id' => $depositType->id, 'period_id' => $id, 'deposit_date' => $period->end_date, 'status' => 0];
                                Deposit::firstOrCreate($deposit_arr, $deposit_arr);
                                $detail = StaffPeriodFeeDetail::query()->create($arr);
                                $dtId = $depositType->id;
                                $staffPeriodFeeDetails = $staffPeriodFeeDetails->reject(function ($v) use ($staffId, $id, $dtId) {
                                    return $v->staff_id == $staffId && $v->period_id == $id && $v->fee_type == DepositType::class && $v->fee_id == $dtId;
                                });
                                $staffPeriodFeeDetails->push($detail);
                            }
                        }
                    }
                    //搬出宿舍的押金费用明细的计算
                    if ($outStaffIds->contains($staffId)) {
                        foreach ($depositTypes as $depositType) {
                            $deposit = Deposit::where([['staff_id', $staffId], ['deposit_type_id', $depositType->id], ['status', 0]])->first();

                            if ($deposit && $deposit->depositType->fee > 0) {
                                $arr = [
                                    'staff_id' => $staffId,
                                    'dorm_id' => $dormId,
                                    'period_id' => $id,
                                    'staff_person_days' => $days,
                                    'dorm_person_days' => $totalDays,
                                    'total_resource_consume' => 0,
                                    'total_money' => 0,
                                    'fee_type' => DepositType::class,
                                    'fee_id' => $depositType->id,
                                    'fee_undertake' => -$deposit->depositType->fee
                                ];
                                $detail = StaffPeriodFeeDetail::query()->create($arr);
                                $staffPeriodFeeDetails->push($detail);
                            }
                        }
                    }
                }
            }

            //创建员工收费主项
            $detailGroups = $staffPeriodFeeDetails->groupBy('staff_id');
            foreach ($detailGroups as $staff_id => $detailGroup) {
                $days = 0;
                $prev_dorm = null;
                foreach ($detailGroup as $item) {
                    if ($prev_dorm != $item->dorm_id) {
                        $days = $days + $item->staff_person_days;
                    }
                    $prev_dorm = $item->dorm_id;
                }
                $arr = [
                    'period_id' => $id,
                    'staff_id' => $staff_id,
                    'deposit_fee' => $detailGroup->where('fee_type', 'App\Models\DepositType')->sum('fee_undertake'),
                    'dorm_days' => $days,
                ];
                foreach ($feeTypes as $feeType) {
                    $arr[$feeType->name] = $detailGroup->where('fee_type', $feeType->model)->sum('fee_undertake');
                }


                $main = StaffPeriodFee::query()->create($arr);
                foreach ($detailGroup as $detail) {
                    $detail->staff_period_fee_id = $main->id;
                    $detail->save();
                }
            }

            $period->status = 1; //变成已经计算状态
            $period->save();

            $response = [
                'message' => 'Period Fee calculated success.',
                'data' => ['created' => $staffDays->count()],
            ];
            if (\request()->wantsJson()) {

                return response()->json($response);
            }
            return redirect()->back()->with('message', $response['message']);
        } catch (\Exception $exception) {
            Log::error($exception->getTraceAsString());
            $period->status = $old_status;
            $period->save();
            if (\request()->wantsJson()) {
                return response()->json([
                    'error' => true,
                    'message' => $exception->getMessage() . ':' . $exception->getTraceAsString()
                ]);
            }
            return redirect()->back()->withErrors($exception->getMessage());
        }
    }

    public function calcSimpleTypeConsumeMoney($feeType, $consume, $month = null)
    {
        switch ($feeType->compute_unit) {
            case '元/计量单位':
                return $consume * $feeType->fee;
            case '元/月/间':
                return $feeType->fee;
            default:
                return $feeType->fee * $consume;
        }
    }

    public function calcSimpleTypeMoneyWithoutConsume($feeType, $staff_cnt)
    {
        switch ($feeType->compute_unit) {
            case '元/月/人':
                return $feeType->fee;
            case '元/月/间':
                return $feeType->fee / $staff_cnt;
            default:
                return $feeType->fee;
        }
    }

    /***
     * 按梯度价直接乘以宿舍用量得到宿舍费用
     * @param $feeType
     * @param $consume
     * @param $month
     * @return float|int
     */
    public function calcTypesConsumeMoneyByMeetOne($feeType, $consume, $month)
    {
        switch ($feeType->compute_unit) {
            case '元/计量单位':
                if ($feeType->season_control) {
                    $feeTypeLevel = ElectricFeeType::where('name', $feeType->name)->where('from_month', '<=', $month)->where('to_month', '>=', $month)->where('level_to', '>=', $consume)->where('level_from', '<', $consume)->first();
                    if ($feeTypeLevel) {
                        return $feeTypeLevel->fee * $consume;
                    } else {
                        return $feeType->fee * $consume;
                    }
                } else {
                    return $feeType->fee * $consume;
                }
            case '元/月/间':
                return $feeType->fee;
            default:
                return $feeType->fee * $consume;
        }
    }

    /***
     * 从最小梯度开始，逐级分用区间用量，区间用量乘以该梯度的价格，然后累加得到总费用
     * @param $feeType
     * @param $consume
     * @param $month
     * @return float|int
     */
    public function calcTypesConsumeMoneyByMeetLevels($feeType, $consume, $month)
    {
        switch ($feeType->compute_unit) {
            case '元/计量单位':
                if ($feeType->season_control) {
                    $feeTypes = ElectricFeeType::where('name', $feeType->name)->where('from_month', '<=', $month)->where('to_month', '>=', $month)->orderBy('level_from')->get();
                    if (count($feeTypes) > 1) {
                        $feeTypes = $feeTypes->filter(function ($type) use ($consume) {
                            return $type->level_to >= $consume;
                        });
                    }
                    $total = 0;
                    foreach ($feeTypes as $t) {
                        if ($t->level_to <= $consume) {
                            $total += $t->fee * ($t->level_to - $t->level_from);
                        } else {
                            $total += $t->fee * ($consume - $t->level_from);
                        }
                    }
                    return $total;
                } else {
                    return $feeType->fee * $consume;
                }
            case '元/月/间':
                return $feeType->fee;
            default:
                return $feeType->fee * $consume;
        }
    }
}
